﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using Microsoft.Phone.Data.Linq;
using System.Data.Linq.SqlClient;
using System.Data.Common;
using System.Threading;
using InstaMe.Source.Model;
using InstaMe.utils;

namespace InstaMe.DB
{
    public class DBManager : DataContext
    {

        private const string DB_NAME = "isostore:/InstaMe.sdf";
        private const int DB_VERSION = 1;
        private const string TABLE_NOT_EXIST_ERROR = "The specified table does not exist";
        private const long PROFILE_EXPIRED_TIME = 7 * 86400000;  // 7Days


        private static DBManager mInstance = null;
        private static object mLock = new object();
        private static int mDBId = 1;

        public Table<DiaryItem> DiaryItemTable;

        private Thread mWokerThead;
        private List<QueryObject> mQueryQueue;

        private DBManager()
            : base(DB_NAME)
        {
            mWokerThead = null;
            mQueryQueue = new List<QueryObject>();
        }

        public static int NEXT_DB_REQUEST_ID()
        {
            return mDBId++;
        }

        public static DBManager getInstance()
        {
            if (mInstance == null)
            {
                lock (mLock)
                {
                    if (mInstance == null)
                    {
                        try
                        {
                            mInstance = new DBManager();
                            if (!mInstance.DatabaseExists())
                            {
                                mInstance.CreateDatabase();
                                DatabaseSchemaUpdater iDbUpdate = mInstance.CreateDatabaseSchemaUpdater();
                                iDbUpdate.DatabaseSchemaVersion = DB_VERSION;
                                iDbUpdate.Execute();
                            }
                            else
                            {
                                //Create the database schema updater
                                DatabaseSchemaUpdater iDbUpdate = mInstance.CreateDatabaseSchemaUpdater();
                                int iDbVersion = iDbUpdate.DatabaseSchemaVersion;
                                if (iDbVersion < DB_VERSION)
                                {
                                    // Mirgate Data here
                                    iDbUpdate.DatabaseSchemaVersion = DB_VERSION;
                                    iDbUpdate.Execute();
                                }
                            }

                            // Verify Table
                            mInstance.VerifyTable<DiaryItem>();
                        }
                        catch
                        {

                        }
                    }
                }
            }
            return mInstance;
        }

        public void SAVE()
        {
            try
            {
                mInstance.SubmitChanges();
            }
            catch
            {

            }
        }

        private void StartQuery(QueryObject aQueryObj, bool isForce = false)
        {
            lock (mQueryQueue)
            {
                if (isForce)
                {
                    mQueryQueue.Insert(0, aQueryObj);
                }
                else
                {
                    mQueryQueue.Add(aQueryObj);
                }
            }
            if (mWokerThead == null)
            {
                mWokerThead = new Thread(new ThreadStart(RUN));
                mWokerThead.Start();
            }
        }

        private void VerifyTable<TEntity>() where TEntity : class
        {
            try
            {
                var iTable = mInstance.GetTable<TEntity>();
                iTable.Any();
            }
            catch (DbException ex)
            {
                if (ex.Message.Contains(TABLE_NOT_EXIST_ERROR))
                {
                    CreateTable<TEntity>();
                }
            }
        }

        private void CreateTable<T>()
        {
            try
            {
                DatabaseSchemaUpdater iDbUpdate = mInstance.CreateDatabaseSchemaUpdater();
                iDbUpdate.AddTable<T>();
                iDbUpdate.Execute();
            }
            catch (Exception ex)
            {

            }
        }
        /***************************************************************************************/
        /* Message Table
        /***************************************************************************************/
        public void InsertOrUpdateDiaryItemAsync(DiaryItem aDiaryItem)
        {
            QueryObject iInsertObj = new QueryObject();
            iInsertObj.setAction(QueryObject.DB_INSERT_DIARY_ITEM);
            iInsertObj.setParam(QueryObject.KEY_OBJECT_INSERT, aDiaryItem);
            StartQuery(iInsertObj);
        }

        public void InsertOrUpdateDiaryItem(DiaryItem aDiaryItem)
        {
            lock (mLock)
            {
                try
                {
                    var iQuery = GetDiaryItemById(this, aDiaryItem.id);
                    DiaryItem iResult = iQuery.FirstOrDefault();
                    if (iResult != null)
                    {
                        iResult.MappingData(aDiaryItem);
                        mInstance.Refresh(RefreshMode.KeepCurrentValues, iResult);
                        mInstance.submitChangesAsync(aDiaryItem);
                    }
                    else
                    {
                        DiaryItemTable.InsertOnSubmit(aDiaryItem);
                        mInstance.submitChangesAsync(aDiaryItem);
                    }
                }
                catch
                {

                }
            }
        }

        public void LoadDiaryItemAsync(IDBListener aListener, long aId)
        {
            QueryObject iLoadObj = new QueryObject();
            iLoadObj.setAction(QueryObject.DB_LOAD_DIARY_ITEM);
            iLoadObj.setListener(aListener);
            iLoadObj.setParam(QueryObject.KEY_FROM_ID, aId);
            StartQuery(iLoadObj);
        }

        public List<DiaryItem> LoadDiaryItem(long aId)
        {
            lock (mLock)
            {
                try
                {
                    var iQuery = GetDiaryFromId(this, aId).ToList();
                    return iQuery;
                }
                catch
                {
                }
            }
            return null;
        }

        public void RemoveDiaryItemAsync(long aRemoveId)
        {
            QueryObject iDeleteObj = new QueryObject();
            iDeleteObj.setAction(QueryObject.DB_REMOVE_DIARY_ITEM);
            iDeleteObj.setParam(QueryObject.KEY_ID_REMOVE, aRemoveId);
            StartQuery(iDeleteObj);
        }

        public void RemoveDiaryItem(long aRemoveId)
        {
            lock (mLock)
            {
                try
                {
                    var iDeleteQuery = GetDiaryItemById(this, aRemoveId).ToList();

                    foreach (DiaryItem iChatBGObj in iDeleteQuery)
                    {
                        DiaryItemTable.DeleteOnSubmit(iChatBGObj);
                    }
                    mInstance.SubmitChanges();
                }
                catch
                {
                }
            }
        }
        /***************************************************************************************/
        /* ChatBackGround Table
        /***************************************************************************************/
        #region DBSubmitchanges
        private long waitSubmitChangesTimer = 0;
        private const long DELTA_SUBMIT_CHANGE = 5000;
        private const int TYPE_INSERT = 0;
        private const int TYPE_DEL = 1;
        private const int TYPE_UPDATE = 2;
        private Timer submitTimer = null;

        private void startSubmitChangeTimer()
        {
            if (submitTimer == null)
            {
                long interval;
                if (waitSubmitChangesTimer == 0)
                {
                    interval = DELTA_SUBMIT_CHANGE;
                }
                else
                {
                    interval = TimeUtils.currentTimeMilis() - waitSubmitChangesTimer;
                    if (interval < DELTA_SUBMIT_CHANGE)
                    {
                        interval = DELTA_SUBMIT_CHANGE - interval;
                    }
                    else
                    {
                        mInstance.submitChangesAsync(null);
                        return;
                    }
                }
                submitTimer = new Timer(submitChangesTick, null, 0, (int)interval + 1);
            }
        }

        private void submitChangesTick(object state)
        {
            if (TimeUtils.currentTimeMilis() - waitSubmitChangesTimer > DELTA_SUBMIT_CHANGE)
            {
                if (submitTimer != null)
                {
                    submitTimer.Dispose();
                    submitTimer = null;
                }
                mInstance.submitChangesAsync(null);
            }
        }

        public void submitChangesAsync(Object changeObject, bool isForce = false)
        {
            if (waitSubmitChangesTimer != 0 || isForce)
            {
                if (TimeUtils.currentTimeMilis() - waitSubmitChangesTimer > DELTA_SUBMIT_CHANGE || isForce)
                {
                    QueryObject iInsertObj = new QueryObject();
                    iInsertObj.setAction(QueryObject.DB_FORCE_SUBMIT_CHANGES);
                    StartQuery(iInsertObj, true);
                }
            }
            else
            {
                waitSubmitChangesTimer = TimeUtils.currentTimeMilis();
            }
        }

        public new void SubmitChanges()
        {
            waitSubmitChangesTimer = 0;
            base.SubmitChanges();
        }

        public void forceSubmitChanges()
        {
            lock (mLock)
            {
                if (mInstance.hasPenddingChange())
                {
                    try
                    {
                        SubmitChanges();
                    }
                    catch
                    {

                    }
                }
            }
        }

        private IList<Object> getChangeListByType(int type)
        {
            try
            {
                ChangeSet changes = base.GetChangeSet();
                switch (type)
                {
                    case TYPE_INSERT:
                        return changes.Inserts;
                    case TYPE_DEL:
                        return changes.Deletes;
                    default:
                        return changes.Updates;
                }
            }
            catch
            {

            }
            return new List<Object>();
        }

        private bool hasPenddingChange()
        {
            return getChangeListByType(TYPE_INSERT).Count > 0
                || getChangeListByType(TYPE_UPDATE).Count > 0
                || getChangeListByType(TYPE_DEL).Count > 0;
        }

        #endregion

        /**************************************************************************************************/
        /* Defined DB Function
        /**************************************************************************************************/
        private Func<DBManager, long, IQueryable<DiaryItem>> GetDiaryItemById =
        CompiledQuery.Compile((DBManager aDB, long aId) =>
            from iDiaryItem in aDB.DiaryItemTable
            where iDiaryItem.id == aId
            select iDiaryItem);

        private Func<DBManager, long, IQueryable<DiaryItem>> GetDiaryFromId =
        CompiledQuery.Compile((DBManager aDB, long aId) =>
           from iDiaryItem in aDB.DiaryItemTable
           where iDiaryItem.id > aId
           orderby iDiaryItem.id descending
           select iDiaryItem);

        /***************************************************************************************/
        /* Query Thread
        /***************************************************************************************/
        private void RUN()
        {
            while (true)
            {
                QueryObject iQueryItem = null;
                lock (mQueryQueue)
                {
                    if (mQueryQueue.Count > 0)
                    {
                        iQueryItem = mQueryQueue.ElementAt(0);
                        mQueryQueue.RemoveAt(0);
                        if (iQueryItem == null)
                        {
                            continue;
                        }
                    }
                    else
                    {
                        break;
                    }
                }

                byte iQueryAction = iQueryItem.getAction();
                IDBListener iListener = iQueryItem.getListener();

                switch (iQueryAction)
                {
                    case QueryObject.DB_FORCE_SUBMIT_CHANGES:
                        forceSubmitChanges();
                        break;
                    case QueryObject.DB_INSERT_DIARY_ITEM:
                        DiaryItem iDiaryItem = (DiaryItem)iQueryItem.getParam(QueryObject.KEY_OBJECT_INSERT);
                        if (iDiaryItem != null)
                        {
                            InsertOrUpdateDiaryItem(iDiaryItem);
                        }
                        break;
                    case QueryObject.DB_LOAD_DIARY_ITEM:
                        {
                            long iFromId = -1;
                            iFromId = (long)(iQueryItem.getParam(QueryObject.KEY_FROM_ID) ?? 0L);

                            if (iFromId != -1 && iListener != null)
                            {
                                List<DiaryItem> iDiaryItemList = LoadDiaryItem(iFromId);
                                iQueryItem.setParam(QueryObject.KEY_LIST_DIARY_ITEM_RETURN, iDiaryItemList);
                                iListener.OnDBComplete(iQueryItem);
                            }
                        }
                        break;
                    case QueryObject.DB_REMOVE_DIARY_ITEM:
                        {
                            long iDiaryItemId = -1;
                            iDiaryItemId = (long)iQueryItem.getParam(QueryObject.KEY_ID_REMOVE);

                            if (iDiaryItemId != -1)
                            {
                                RemoveDiaryItem(iDiaryItemId);
                            }
                        }
                        break;
                    default:
                        break;
                }
            }

            if (waitSubmitChangesTimer != 0)
            {
                startSubmitChangeTimer();
            }
            mWokerThead = null;
        }

        public static List<TSource> SafeToDictionary<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Dictionary<TKey, TSource> resultDic)
        {
            List<TSource> wrongMember = new List<TSource>();
            if (source == null || keySelector == null)
            {
                return null;
            }

            foreach (TSource element in source)
            {
                TKey key = keySelector(element);
                if (!resultDic.ContainsKey(key))
                {
                    resultDic.Add(key, element);
                }
                else
                {
                    wrongMember.Add(element);
                }
            }

            return wrongMember;
        }
    }
}
